package app;

import gui.ApplicationGui;
import gui.ParListGui;

import java.util.LinkedList;
import java.util.List;

import messages.ConnectRsp;
import messages.DisconnectRsp;
import messages.ParListElement;
import messages.ParListRsp;
import messages.SingleTuneRsp;
import messages.TuneData;
import util.Utils;

public class Decoder implements Runnable{

	private byte [] msg;
	
	private static boolean				tuneDataMessage	= false;
	
	private static List<ParListElement>	parListElements	= new LinkedList<ParListElement>( );
	
	private static ApplicationGui gui;
	
	private short destID;
	private short srcID;
	private short length;
	private short commandID;
	private short mpbHd;
	
	public Decoder(byte [] _msg, ApplicationGui _gui){
		this.msg = _msg;
		this.gui = _gui;
	}
	
	@Override
	public void run( ) {
		checkMessage( msg );
	}
	
	/**
	 * Checks which message is it
	 * 
	 * @param message
	 */
	public void checkMessage( byte[] message ) {

		System.out.println("DECODER has started decoding a new Message ...");
		
		//System.out.println( "TuneDataMessage = " + tuneDataMessage );

		if ( !tuneDataMessage ) {
			if ( message[0] == 0x1b && message[1] != 0xFF ) {

				length = Utils.bytesToShort(message[2],message[1]);
				
				// retiramos os respectivos campos do header
				destID = Utils.bytesToShort( message[4] , message[3] );
				srcID = Utils.bytesToShort( message[6] , message[5] );
				commandID = Utils.bytesToShort( message[8] , message[7] );


				// CONNECT_RSP 
				if ( commandID == Utils.CONNECT_RSP_ID ) {
					decodeConnectRsp(message);
				}

				// DISCONNECT_RSP
				if ( commandID == Utils.DISCONNECT_RSP_ID ) {
					decodeDisconnectRsp( message );
				}

				// PAR_LIST_RSP
				if ( commandID == Utils.PAR_LIST_RSP_ID ) {
					decodeParListRsp( message );
				}

				// SINGLE_TUNE_RSP
				if ( commandID == Utils.SINGLE_TUNE_RSP_ID ) {
					decodeSingleTuneRsp(message);
				}
			}
		} else { // tune data message
			decodeTuneData( message );
		}
	}

	private void decodeTuneData( byte[] message ) {
		System.out.print( "Message TUNE_DATA received : " );
		for ( int i = 0 ; i < message.length ; i++ ) {
			System.out.print( " | " + message[i] + " | " );
		}
		System.out.println( );

		if ( message[0] == 0x1b && message[1] != 0xFF ) {

			length = Utils.bytesToShort(message[2],message[1]);
			destID = Utils.bytesToShort( message[4] , message[3] );
			srcID = Utils.bytesToShort( message[6] , message[5] );
			mpbHd = Utils.bytesToShort( message[8] , message[7] );

			byte recSize = message[10];
			byte mpbLen = message[9];

			short[] mpbData = new short[mpbLen];

			if ( mpbLen > 0 ) {
				for ( int i = 11 , t = 0 ; t < mpbData.length ; i++ , t++ ) {
					// CHECK position
					mpbData[t] = Utils.bytesToShort( message[i + 1] , message[i] );
				}

			}

			TuneData tdata = new TuneData( destID , srcID , mpbHd , recSize , mpbLen , mpbData );
			//System.out.println( tdata.toString( ) );
			gui.appendText( tdata +"\n" );

			tuneDataMessage = false;
		}
		
	}

	private void decodeSingleTuneRsp( byte[] message ) {
		System.out.print( "Message SINGLE_TUNE_RSP received : " );
		for ( int i = 0 ; i < message.length ; i++ ) {
			System.out.print( " | " + message[i] + " | " );
		}
		System.out.println( );

		short tuneID = Utils.bytesToShort( message[10] , message[9] );
		
		System.out.println( "\n Len = " + length );

		List<ParListElement> msgIDs = new LinkedList<ParListElement>( );
		List<Short> mpbHds = new LinkedList<Short>( );
		List<Byte> recLens = new LinkedList<Byte>( );
		List<Byte> recIDs = new LinkedList<Byte>( );
		List<List<Short>> words = new LinkedList<List<Short>>( );

		int actualByte = 11;

		while ( actualByte < length ) {

			msgIDs.add( new ParListElement( Utils.bytesToShort( message[actualByte+1] , message[actualByte] ), 
											Utils.bytesToShort( message[actualByte+3] , message[actualByte+2] ),
											Utils.bytesToShort( message[actualByte+5] , message[actualByte+4] ) ,
											message[actualByte+6], 
											message[actualByte+7],
											message[actualByte+8], null )); 
			actualByte += 10;

			mpbHds.add( Utils.bytesToShort( message[actualByte + 1] ,message[actualByte] ) );
			actualByte += 2;

			int numWords = (message[actualByte + 1] & 0x000000ff);
			recLens.add( message[actualByte + 1] );
			recIDs.add( message[actualByte] );
			actualByte += 2;

			List<Short> tempW = new LinkedList<Short>( );
			if ( numWords > 0 ) {
				while ( numWords > 0 ) {
					tempW.add( Utils.bytesToShort( message[actualByte + 1] ,message[actualByte] ) );
					actualByte += 2;
					numWords--;
				}
			}
			words.add( tempW );
		}

		if( mpbHds != null && mpbHds.get( 0 ) != null && mpbHds.get(0) > 0 )
			tuneDataMessage = true;

		SingleTuneRsp singleTRes = new SingleTuneRsp( destID , srcID , tuneID ,
				convertToParListElementsArray( msgIDs ) , convertToShortArray( mpbHds ) ,
				convertToByteArray( recLens ) , convertToByteArray( recIDs ) ,
				convertToShortListArray( words ) );

		//System.out.println( singleTRes );
		gui.appendText( singleTRes +"\n" );
		
	}

	private void decodeParListRsp( byte[] message ) {
		byte messageCount_actual = message[10];
		byte messageCount_total = message[9];

		short sourceID;
		short channelID;
		short msgType;
		byte sourceNo;
		byte channelNo;
		byte layer;
		short[] chars;

		int actualByte = 11;
		while ( actualByte < length ) {

			sourceID = Utils.bytesToShort( message[actualByte + 1] ,message[actualByte] );
			actualByte += 2;

			channelID = Utils.bytesToShort( message[actualByte + 1] ,message[actualByte] );
			actualByte += 2;

			msgType = Utils.bytesToShort( message[actualByte + 1] , message[actualByte] );
			actualByte += 2;

			sourceNo = message[actualByte + 1];
			channelNo = message[actualByte];
			actualByte += 2;

			layer = message[actualByte + 1];
			// do'nt forget to read NULL byte
			actualByte += 2;

			chars = new short[Utils.ASCII_ID_LENGTH];
			for ( int j = 0 ; j < Utils.ASCII_ID_LENGTH ; j++ , actualByte += 2 )
				chars[j] = Utils.bytesToShort( message[actualByte + 1] ,
						message[actualByte] );

			synchronized ( parListElements ) {
				parListElements.add( new ParListElement( sourceID , channelID , msgType ,
						sourceNo , channelNo , layer , chars ) );	
			}
		}
		
		ParListRsp parRsp = new ParListRsp( destID , srcID , messageCount_actual ,
				messageCount_total , convertToParListElementsArray( parListElements ) );

		//System.out.println( parRsp.toString( ) );
		gui.appendText( parRsp +"\n" );

		if ( messageCount_actual == messageCount_total )
			new ParListGui( gui , parListElements );
		
	}

	private void decodeDisconnectRsp( byte[] message ) {
		short resp = Utils.bytesToShort( message[10] , message[9] );
		DisconnectRsp dRsp = new DisconnectRsp( destID , srcID , resp );
		
		//System.out.println( dRsp.toString( ) );
		gui.appendText( dRsp +"\n" );
	}

	private void decodeConnectRsp( byte[] message ) {
		short window_s = Utils.bytesToShort( message[10] , message[9] );
		short compat = Utils.bytesToShort( message[12] , message[11] );
		byte error = message[14];
		ConnectRsp conRsp = new ConnectRsp( destID , srcID , window_s , compat , error );
		
		//System.out.println( conRsp.toString( ) );
		gui.appendText( conRsp +"\n" );
	}

	private static ParListElement[] convertToParListElementsArray( List<ParListElement> list ) {
		synchronized ( list ) {
			ParListElement[] array = new ParListElement[list.size( )];
			for ( int i = 0 ; i < array.length ; i++ )
				array[i] = list.get( i );

			return array;	
		}
	}

	private static List[] convertToShortListArray( List<List<Short>> list ) {
		List[] array = new List[list.size( )];
		for ( int i = 0 ; i < array.length ; i++ )
			array[i] = list.get( i );

		return array;
	}

	private static short[] convertToShortArray( List<Short> list ) {
		short[] array = new short[list.size( )];
		for ( int i = 0 ; i < array.length ; i++ )
			array[i] = list.get( i );

		return array;
	}

	private static byte[] convertToByteArray( List<Byte> list ) {
		byte[] array = new byte[list.size( )];
		for ( int i = 0 ; i < array.length ; i++ )
			array[i] = list.get( i );

		return array;
	}
}
